Fork me on GitHub

IO

注意:所有文章除特别说明外,转载请注明出处.

二进制I/O

Java中处理输入输出,File对象封装文件或路径属性,不包含从/向文件读/写数据的方法。为了进行I/O操作,需要使用正确的Java I/O类创建对象。

1.File 类

//检查指定路径下是否存在指定目录或者文件
File file = new File("c:\\a.txt");//在c:目录下创建一个file文件
System.out.println(file.exists());//指定文件是否存在

//File对象是否是目录
System.out.println(file.isDirectory());

//对象是否是文件
System.out.println(file.isFile());

注意:构建一个File实例不会在机器上创建一个文件,不管文件是否存在都可以创建任意文件名的File实例,调用File实例的exists()方法判断文件或目录是否存在。

//创建一个File类实例
new File(String pathname);
//通过制定路径创建一个新的File实例
new File(String parent,String child);

总结:I/O类可以分为文本I/O和二进制I/O,文本I/O将数据解释成字符序列,二进制I/O将数据解释成原始的二进制数值。Java自动完成对文本I/O的编码和解码。

File 类中常用的方法:
    1.createNewFile():在指定的位置创建一个文件,成功返回true,如果已经存在就不创建并返回false。
    2.mkdir():在指定位置创建目录,这只会创建最后一级目录,如果上级目录不存在就抛出异常。
    3.mkdirs():在指定位置创建目录,这会创建路径中所有不存在的目录。

    exists() 文件或文件夹是否存在。
    isFile() 是否是一个文件,如果不存在,则始终为false。
    isDirectory() 是否是一个目录,如果不存在,则始终为false。
    isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
    isAbsolute() 测试此抽象路径名是否为绝对路径名。

    getName() 获取文件或文件夹的名称,不包含上级路径。
    getPath() 返回绝对路径,可以是相对路径,但是目录要指定
    getAbsolutePath() 获取文件的绝对路径,与文件是否存在没关系
    length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
    getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
    lastModified() 获取最后一次被修改的时间。

    文件夹相关:
        staic File[] listRoots() 列出所有的根目录(Window中就是所有系统的盘符)
        list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
        list(FilenameFilter filter)    返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
        listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
        listFiles(FilenameFilter filter) 返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。

案例:

//列出指定目录下所有包含的子文件与子目录
public static void listAllFileAndDirs(String path){
    //1.创建File对象,表示这个目录
    File dir = new File(path);
    //2.通过list方法得到所包含的所有目录与子文件的名称
    String[] names = dir.list();
    //3.显示这些名称
    for(int i = 0; i < names.length; i++){
        System.out.println(names[i]);
    }
}

//在上面案例的基础上判断哪些是文件,哪些是文件夹
public static void listAllFilesAndDirs2(String path){
    //1.创建File对象,表示这个目录
    File dir = new File(path);
    //2.通过listFiles()方法得到所包含的所有子目录与子文件名称
    File[] names = dir.listFiles();
    //3.分别显示文件夹名与文件名
    for(int i = 0; i < names.length; i++){
        File file = names[i];
        if(file.isFile()){
            System.out.println("文件:");
            System.out.println("\t"+file.getName());
        }else if(file.isDirectory()){
            System.out.println("文件夹:");
            System.out.println("\t"+file.getName());
        }
    }
}

//或者通过集合的形式实现,列出所有的文件以及文件夹
public static void listAllFilesAndDirs(String path) {
    //1.创建File对象表示这个目录
    File dir = new File(path);

    //2.通过listFiles方法得到所包含的所有子目录与子文件名称
    File[] names = dir.listFiles();

    //3.得到所有的文件名集合,与所有的文件夹名集合
    List<File> filesList = new ArrayList<File>();
    List<File> dirsList = new ArrayList<File>();
    for (int i = 0; i < names.length; i++) {
        File file = names[i];
        //不管是用什么方式都需要用File相关的方法判断其是否是文件或者文件夹
        if (file.isFile()) {
            filesList.add(file);
        } else if (file.isDirectory()) {
            dirsList.add(file);
        }
    }

    //4.分别显示文件名与文件夹名
    System.out.println("子文件:");
    for (int i = 0; i < filesList.size(); i++) {
        System.out.println("\t" + filesList.get(i).getName());
    }
    System.out.println("子目录:");
    for (int i = 0; i < dirsList.size(); i++) {
        System.out.println("\t" + dirsList.get(i).getName());
    }
}

//列出指定目录下所有扩展名为.java的文件
public class FileTest2 {
    public static void main(String[] args) {
        String path = "c:" + File.separatorChar + "test";
        File file = new File(path);
        listtAllFiles(file, "java");
    }

    public static void listtAllFiles(File dir, String extension) {
        // 1.获取所有的子文件和子文件夹
        File[] files = dir.listFiles();

        // 2.从中找出符合条件的文件并显示出来
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            // 3.需要以指定文件后缀结尾才算符合条件
            if (file.getName().endsWith(extension)) {
                System.out.println(file.getName());
            }
        }
    }
}

//给FilenameFilter接口写一个工具类,可以传递指定的过滤规则,从指定目录中找出指定扩展名的文件,并列出来
public class FileTest2 {
    public static void main(String[] args) {
        String path = "c:" + File.separatorChar + "test";
        File file = new File(path);
        listtAllFiles2(file, "txt");
    }

    public static void listtAllFiles2(File dir, String name) {
        // 1.获取所有的子文件和子文件夹
        String[] files = dir.list(new DirFilter("txt"));

        // 2.显示名称
        for (int i = 0; i < files.length; i++) {
            System.out.println(files[i]);
        }
    }
}

class DirFilter implements FilenameFilter {
    private String extension;

    public DirFilter() {

    }

    public DirFilter(String extension) {
        this.extension = extension;
    }

    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(extension);
    }
}

2.IO流技术

通过Java程序读取文件中的内容以及将程序中的数据保存早硬盘的文件中去都需要用到IO流技术。

案例:

//使用IO流技术读写文件
public class IOTest{
    public static void mian(String[] args) throws FileNotFoundException,IOException {
        writeFileTest();
        readFileTest();
    }
    private static void writeFileTest() throws FileNotFoundException,IOException {
        //1.创建文件对象
        File file = new File("c:\\a.txt");
        //2.创建文件输出流
        FileOutputStream fos = new FileOutputStream(file);
        fos.write('g');
        fos.write('z');
        fos.write('i');
        fos.write('t');
        fos.write('c');
        fos.write('a');
        fos.write('s');
        fos.write('t');
        fos.close();

    }
    private static void readFileTest() throws FileNotFoundException,IOException {
    // 1.创建文件对象
    File file = new File("c:\\a.txt");
    // 2.创建文件输入流
    FileInputStream fis = new FileInputStream(file);
    // 3.有对多长,就读多少字节。
    for (int i = 0; i < file.length(); i++) {
        System.out.print((char) fis.read());
    }
    //完成读写操作之后,应该通过调用close()方法来关闭它,释放有限的系统资源
    fis.close();
}
}

注意:读取硬盘上的文件应该使用输入流(InputStream),但是InputStream是抽象类,具体使用时使用具体实现类来创建对象(FileInputStream)。

//输入流读取方式1:read()方法
//根据read()方法返回值的特性,如果读到文件的末尾返回-1,如果不为-1继续往下读
private static void showContent(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    int len = fis.read();
    while(len != -1){
        System.out.println((char)len);
        len = fis.read();
    }
    //使用完之后关闭流
    fis.close();
}

...

注意:Java文件向指定文件中写入数据,使用字节流对象OutputStream。该类是抽象类,使用具体实现类FileOutputStream创建其流对象。

//输出流写出方式1:write(int b)方法
import java.io.FileOutputStream;
import java.io.IOException;

public class IOTest2 {
    public static void main(String[] args){
        String path = "c:\\a.txt";
        writeTxtFile(path);
    }
    private static void writeTxtFile(String path) throws IOException {
        //1.打开文件输出流,流的目的地是指定的文件
        FileOutputStream fos = new FileOutputStream(path);
        //2.通过流向文件写数据
        fos.write('j');
        fos.write('a');
        fos.write('v');
        fos.write('a');
        //3.用完流后关闭流
        fos.close();
    }
}

提示:如果在对应的path路径下不存在相应的文件时,其会自动创建一个,但不是创建多级目录。

//输出流写出方式2:write(byte[] b) 使用缓冲,提高效率
public class IoTest2 {
    public static void main(String[] args) throws IOException {
        String path = "c:\\a.txt";
        writeTxtFile(path);
    }
    private static void writeTxtFile(String path) throws IOException {
        //1.打开文件输出流,流的目的地是指定的文件
        FileOutputStream fos = new FileOutputStream(path);

        //2.通过流向文件写数据
        byte[] byt = "java".getBytes();
        fos.write(byt);

        //3.用完流后关闭流
        fos.close();
    }
}

//字节输入/输出流综合使用
public class IoTest3 {
    public static void main(String[] args) throws IOException {
        String path = "c:\\b.txt";
        String content = "hello java";

        writeFile(path, content);

        readFile(path);
    }

    public static void writeFile(String path, String content)
            throws IOException {
        // 打开文件输出流
        FileOutputStream fos = new FileOutputStream(path);
        byte[] buffer = content.getBytes();
        // 向文件中写入内容
        fos.write(buffer);
        // 关闭流
        fos.close();

    }

    public static void readFile(String path) throws IOException {
        FileInputStream fis = new FileInputStream(path);
        byte[] byt = new byte[1024];
        int len = 0;
        while ((len = fis.read(byt)) != -1) {
            System.out.println(new String(byt, 0, len));
        }
        // 关闭流
        fos.close();

    }
}

//文件的拷贝 方式1
public class IoTest3 {
    public static void main(String[] args) throws IOException {
        String srcPath = "c:\\a.txt";
        String destPath = "d:\\a.txt";
        copyFile(srcPath, destPath);
    }
    public static void copyFile(String srcPath, String destPath)
            throws IOException {

        // 打开输入流,输出流
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);

        // 读取和写入信息
        int len = 0;
        while ((len = fis.read()) != -1) {
            fos.write(len);
        }

        // 关闭流
        fis.close();
        fos.close();
    }
}

//图片/视频/音频的拷贝
public class IoTest3 {
    public static void main(String[] args) throws IOException {

        String srcPath = "c:\\秋.jpg";
        String destPath = "d:\\秋.jpg";
        copyFile(srcPath, destPath);
    }
    public static void copyFile(String srcPath, String destPath)
            throws IOException {
        // 打开输入流,输出流
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);

        // 读取和写入信息
        int len = 0;
        while ((len = fis.read()) != -1) {
            fos.write(len);
        }

        // 关闭流
        fis.close();
        fos.close();
    }
}

//字节流拷问文件 方式2
//使用字节数组作为缓冲区
public static void copyFile2(String srcPath, String destPath)
        throws IOException {
    // 打开输入流,输出流
    FileInputStream fis = new FileInputStream(srcPath);
    FileOutputStream fos = new FileOutputStream(destPath);

    // 读取和写入信息
    int len = 0;

    // 使用字节数组,当做缓冲区
    byte[] byt = new byte[1024];
    while ((len = fis.read(byt)) != -1) {
        fos.write(byt);
    }

    // 关闭流
    fis.close();
    fos.close();
}

字节流的异常处理

案例:

public class IoTest4 {
    public static void main(String[] args) throws IOException,InterruptedException {
        String path = "c:\\b.txt";
        readFile(path);
    }

    private static void readFile(String path) throws IOException,InterruptedException {
        FileInputStream fis = new FileInputStream(path);
        byte[] byt = new byte[1024];
        int len = fis.read(byt);
        System.out.println(new String(byt, 0, len));
        // 让程序睡眠,无法执行到close方法。
        Thread.sleep(1000 * 10);
        fis.close();
    }
}

提示:在上面的案例中如果进程Thread没有睡醒便不能执行关闭操作,所以这样的异常一般使用try…catch块来处理。

3.字节缓冲流

提供专门的字节缓冲流来提高效率,BufferedInputStream和BufferedOutputStream。虽然使用缓冲流来提升效率时,对于小文件看不到性能的提升,但是对于稍微大一些的文件就可以看到实质性的性能的提升。

public class IOTest{
    public static void main(String[] args){
        String srcPath = "c:\\a.mp3";
        String destPath = "d:\\copy.mp3";
        copyFile(srcPath,destPath);
    }
    public static void copyFile(String srcPath,String destPath) throws IOException {
        //1.打开输入输出流
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);
        //2.使用缓冲流
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //3.读取和写入信息
        int len = 0;
        while((len = bis.read()) != -1){
            bos.write(len);
        }
        //关闭流
        bis.close();
        bos.close();
    }
}

4.字符流

字符流=字节流+ 编码表,字符流的抽象基类:Reader/Writer 这些类派生出来的子类名称以其父类名作为子类名的后缀。如:FileReader/FileWriter

1.Reader

使用Reader读取文本,可以使用InputStreamReader适配器从任何一种输入流获取一个Reader。

public class IoTest1_Reader {
    public static void main(String[] args) throws Exception {
        String path = "c:/a.txt";
        // readFileByInputStream(path);
        readFileByReader(path);
    }

    //使用字节流读取文件内容
    public static void readFileByInputStream(String path) throws Exception {
        InputStream in = new FileInputStream(path);

        int len = 0;
        while ((len = in.read()) != -1) {
            System.out.print((char) len);
        }
        in.close();
    }

    //使用字符流读取文件内容
    public static void readFileByReader(String path) throws Exception {
        Reader reader = new FileReader(path);
        int len = 0;
        while ((len = reader.read()) != -1) {
            System.out.print((char) len);
        }
        reader.close();
    }
}

2.Writer

1.write(ch): 将一个字符写入到流中。<br>
2.write(char[]): 将一个字符数组写入到流中。<br>
3.write(String): 将一个字符串写入到流中。<br>
4.flush():刷新流,将流中的数据刷新到目的地中,流还存在。<br>
5.close():关闭资源,在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭。

//将文本数据存储到一个文件中
public class IoTest2_Writer {
    public static void main(String[] args) throws Exception {
        String path = "c:/ab.txt";
        writeToFile(path);
    }

    //写指定数据到指定文件中
    public static void writeToFile(String path) throws Exception {
        Writer writer = new FileWriter(path);
        writer.write('中');
        writer.write("世界".toCharArray());
        writer.write("中国");
        writer.close();
    }
}

注意:程序执行完打开文件,发现没有内容写入。原来需要使用flush方法,刷新该流的缓冲。现在只需要调用close()方法即可,因为close()方法调用了flush()方法。

//字符流拷贝文件 方式1 这而是读一个字符,然后再写一个字符,效率不高
public static void main(String[] args) throws Exception {
    String path1 = "c:/a.txt";
    String path2 = "c:/b.txt";

    copyFile(path1, path2);
}

//使用字符流拷贝文件
public static void copyFile(String path1, String path2) throws Exception {
    Reader reader = new FileReader(path1);
    Writer writer = new FileWriter(path2);

    int ch = -1;
    while ((ch = reader.read()) != -1) {
        writer.write(ch);
    }
    //关闭字符流
    reader.close();
    writer.close();
}

//字符流拷贝文件 方式2
public static void main(String[] args){
    String path1 = "c:\\a.txt";
    String path2 = "c:\\b.txt";
    copyFile(path1,path2);
}

//拷贝文件
public static void copyFile3(String path1,String path2){
    Reader reader = new FileReader(path1);
    Writer writer = new FileWriter(path2);

    int ch = -1;
    char [] arr = new char[1024];
    while((ch = reader.read(arr)) != -1){
        writer.write(arr,0,ch);
    }
    reader.close();
    writer.close();
}

提示:字符流不可以拷贝视频以及音频等文件,是因为计算机中所有的信息都是以二进制形式进行存储的,在读取文件的时候,字符流自动对这些二进制按照码表进行编码处理,但图片本身就是二进制文件,不需要编码。字符流只能拷贝以字符为单位的文本文件(以ASCII码为例是127个,并不是所有的二进制都可以找到对应的ASCII,有些对不上的,就会丢失信息。)

3.字符流的异常处理

public static void main(String[] args) throws Exception {
    //首先创建两个File路径文件
    String path1 = "c:/a.txt";
    String path2 = "c:/b.txt";
    //调用拷贝文件函数
    copyFile2(path1, path2);
}

    //使用字符流拷贝文件,有完善的异常处理
    public static void copyFile2(String path1, String path2) {
        Reader reader = null;
        Writer writer = null;
        try {
            // 打开流
            reader = new FileReader(path1);//Java文件读取对应路径下的文件
            writer = new FileWriter(path2);//Java文件中写入到对应的路径文件中

            // 进行拷贝
            int ch = -1;
            while ((ch = reader.read()) != -1) {
                writer.write(ch);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭流,注意一定要能执行到close()方法,所以都要放到finally代码块中
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    if (writer != null) {
                        writer.close();
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

4.字符流的缓冲区

public class IOTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader reader = new FileReader(path);
        BufferedReader br = new BufferedReader(read);
        String line = null;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
    }
}

注意:在使用缓冲区对象时,我们应该明确缓冲的存在只是为了增强流的功能而存在,所以在建立缓冲区对象时,要先有流对象的存在。缓冲区的出现提高了对流的操作效率。其实就是将数组进行封装。

//使用字符流缓冲区拷贝文本文件
public class Demo {
    public static void main(String[] args) throws IOException {
        //1.关联源文件
        File srcFile = new File("c:\\a.txt");
        //2.关联目标文件
        File destFile = new File("d:\\b.txt");
        //3.实现拷贝
        copyFile(srcFile,destFile);
    }
    private static void copyFile(File srcFile,File destFile) throws IOException {
        //1.创建字符输入流
        FileReader fr = new FileReader(srcFile);
        //2.创建字符输出流
        FileWriter fw = new FileWriter(destFile);

        //3.字符输入流的缓冲流
        BufferedReader br = new BufferedReader(fr);
        //4.字符输出流的缓冲流
        BufferedWriter bw = new BufferedWriter(fw);

        String line = null;
        //一次读取一行
        while((line = br.readLine()) != null) {
            //一次读一行
            bw.write(line);
            //刷新缓冲
            bw.flush();
            //进行换行,由于readLine()方法默认没有换行,需要手动换行
            bw.newLine();
        }
        //关闭流
        br.close();
        bw.close();
    }
}

4.装饰者模式

案例1:在读取的文件的每一行添加行号

public class IoTest7_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);

        BufferedReader br = new BufferedReader(read);
        int count = 0;
        String line = null;
        while ((line = br.readLine()) != null) {
            count++;
            System.out.println(count+":"+line);        
        }

    }
}

案例2:每次使用BufferedReader输出时都需要显示行号

public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }
    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);
        BufferedReader br = new MyBufferedReader(read);
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }
}
class MyBufferedReader extends BufferedReader {
    public MyBufferedReader(Reader read) {
        super(read);
    }
    int count;
    @Override
    public String readLine() throws IOException {
        String line = super.readLine();
        if (line != null) {
            count++;
            return count + ":" + line;
        } else {
            return null;
        }
    }
}

案例3:在输出的一行前加上引号,可以再定义一个BufferedReader的子类,继承BufferedReader增强功能。

public class IOTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }
    private static void readFile(String path) throws IOException {
        Reader reader = new FileReader(path);
        BufferedReader br = new MyQutoBufferedReader(read);
        int count = 0;
        String line = null;
        while((line = br.readLine()) != null) {
            System.out.println(line);
            count++;
        }
    }
}

//quotation 引号
class MyQutoBufferedReader extends BufferedReader {
    public MyQutoBufferedReader(Reader reader){
        super(reader);
    }
    public String readLine() throws IOException {
        String line = super.readLine();
        if(line != null){
            return "\"+line+"\";
        }else{
            return null;
        }
    }
}

案例4:既要显示行号,也要显示引号

public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }
    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);
        BufferedReader bufferedReader = new BufferedReader(read);
        BufferedReader br = new MyQutoBufferedReader2(bufferedReader);
        br = new MyLineBufferedReader2(br);
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }
}
// quotation 引号
class MyQutoBufferedReader2 extends BufferedReader {
    private BufferedReader bufferedReader;
    public MyQutoBufferedReader2(BufferedReader bufferedReader) {
        super(bufferedReader);
        this.bufferedReader = bufferedReader;
    }
    public String readLine() throws IOException {
        String line = super.readLine();
        if (line != null) {
            return "\"" + line + "\"";
        } else {
            return null;
        }
    }
}
class MyLineBufferedReader2 extends BufferedReader {
    private BufferedReader bufferedReader;
    public MyLineBufferedReader2(BufferedReader bufferedReader) {
        super(bufferedReader);
        this.bufferedReader = bufferedReader;
    }
    int count;
    @Override
    public String readLine() throws IOException {
        String line = super.readLine();
        if (line != null) {
            count++;
            return count + ":" + line;
        } else {
            return null;
        }
    }
}

装饰者模式:1.使用分层对象来动态透明的向单个对象中添加功能。2.装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。3.某些对象是可装饰的,可以通过将其它类包装在这个可装饰对象的周围,来将功能分层。4.装饰器必须具有和它装饰的对象相同的接口。


本文标题:IO

文章作者:Bangjin-Hu

发布时间:2018年11月25日 - 09:22:26

最后更新:2020年03月30日 - 08:11:57

原始链接:http://bangjinhu.github.io/undefined/Java 二进制IO/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.